
#include "RtBase.h"
#include "RtHttpMozillaProfile.h"

#ifdef RT_WIN32
#include <shlobj.h>
#endif // RT_WIN32

//
// the nr_* functions are copyed from mozilla\modules\libreg\src 
// with slight modification and only implement 'read & find' method.
// more reference can be found in mozilla source code.
//
static WORD nr_ReadShort(const char *buffer);
static DWORD nr_ReadLong(const char *buffer);
static REGOFF nr_TranslateKey( REGFILE *reg, RKEY key );
static BOOL nr_InitStdRkeys( REGFILE *reg );
static BOOL nr_RegAddKey(REGFILE *reg, RKEY key, char *path, RKEY *newKey);
static BOOL nr_ReadFile(FILE *fh, REGOFF offset, INT32 len, void *buffer);
static BOOL nr_ReadName(REGFILE *reg, REGDESC *desc, DWORD buflen, char *buf);
static BOOL nr_ReadDesc(REGFILE *reg, REGOFF offset, REGDESC *desc);
static BOOL nr_FindAtLevel(REGFILE *reg, REGOFF offset, const char *pName,
                             REGDESC *pDesc, REGOFF *pOffPrev);
static BOOL nr_Find(REGFILE *reg, REGOFF offParent, const char *pPath,
					REGDESC *pDesc, REGOFF *pPrev, REGOFF *pParent);

#define kRegistryProfileSubtreeString "Profiles"
#define kRegistryCurrentProfileString "CurrentProfile"
#define kRegistryNCProfileNameString "NCProfileName"
#define kRegistryDirectoryString "directory"

CRtHttpMozillaProfile::
CRtHttpMozillaProfile(IRtHttpProxySetting::BrowseType aBrowserType)
	: m_BrowserType(aBrowserType)
{
	m_newRegFile.fh = NULL;
}

CRtHttpMozillaProfile::~CRtHttpMozillaProfile()
{
	if (m_newRegFile.fh)
		fclose(m_newRegFile.fh);
}

BOOL CRtHttpMozillaProfile::OpenRegFile(const CRtString &aFileName)
{
	BOOL bRet = FALSE;
	RT_ASSERTE_RETURN(!aFileName.empty() && !m_newRegFile.fh, bRet);
	RT_INFO_TRACE("CRtHttpMozillaProfile::OpenRegFile,"
		" aFileName=" << aFileName << " m_BrowserType=" << m_BrowserType);
	long lLen = 0;

	m_newRegFile.fh = fopen(aFileName.c_str(), "rb");
	if (!m_newRegFile.fh) {
		RT_ERROR_TRACE("CRtHttpMozillaProfile::OpenRegFile, aFileName=" << aFileName);
		return bRet;
	}

	char hdrBuf[sizeof(REGHDR)];
	if (!nr_ReadFile(m_newRegFile.fh, 0, sizeof(REGHDR), hdrBuf))
		goto failed;

	m_newRegFile.hdrDirty = 0;
	m_newRegFile.inInit = TRUE;
	m_newRegFile.filename = NULL;
	m_newRegFile.refCount = 0;

	m_newRegFile.hdr.magic    = nr_ReadLong ( hdrBuf + HDR_MAGIC );
	m_newRegFile.hdr.verMajor = nr_ReadShort( hdrBuf + HDR_VERMAJOR );
	m_newRegFile.hdr.verMinor = nr_ReadShort( hdrBuf + HDR_VERMINOR );
	m_newRegFile.hdr.avail    = nr_ReadLong ( hdrBuf + HDR_AVAIL );
	m_newRegFile.hdr.root     = nr_ReadLong ( hdrBuf + HDR_ROOT );

	if (m_newRegFile.hdr.magic != MAGIC_NUMBER) {
		RT_ERROR_TRACE("CRtHttpMozillaProfile::OpenRegFile, magic=" << m_newRegFile.hdr.magic);
		goto failed;
	}

	/* Check registry version
	 * If the major version is bumped we're incompatible
	 * (minor version just means some new features were added)
	 */
	if (m_newRegFile.hdr.verMajor > MAJOR_VERSION) {
		RT_ERROR_TRACE("CRtHttpMozillaProfile::OpenRegFile, verMajor=" << m_newRegFile.hdr.verMajor);
		goto failed;
	}

	if (fseek(m_newRegFile.fh, 0, SEEK_END) || (lLen = ftell(m_newRegFile.fh)) == -1)  {
		RT_ERROR_TRACE("CRtHttpMozillaProfile::OpenRegFile, lLen=" << lLen);
		goto failed;
	}
	if (lLen != m_newRegFile.hdr.avail) {
		m_newRegFile.hdr.avail = lLen;
		m_newRegFile.hdrDirty = 1;
	}

	bRet = nr_InitStdRkeys(&m_newRegFile);
	if (!bRet) {
		RT_ERROR_TRACE("CRtHttpMozillaProfile::OpenRegFile, nr_InitStdRkeys");
		goto failed;
	}

	m_newRegFile.inInit = FALSE;
	return bRet;

failed:
	fclose(m_newRegFile.fh);
	m_newRegFile.fh = NULL;
	return bRet;
}

BOOL CRtHttpMozillaProfile::GetCurrentProfileName(CRtString &aRetval)
{
	BOOL bRet = FALSE;
#ifdef RT_UNIX
	// there is no register file in Netscape 4.x on Solaris
	if (m_BrowserType == IRtHttpProxySetting::BROWSER_NETSCAPE1) {
		LPCSTR pszHome = getenv("HOME");
		if (pszHome) {
			aRetval = pszHome;
			aRetval += "/.netscape/preferences.js";
			bRet = TRUE;
		}
		return bRet;
	}
#endif // RT_UNIX

	if (m_BrowserType == IRtHttpProxySetting::BROWSER_FIREFOX) {
		CRtString strNativePath;
		bRet = GetNativeRegName(strNativePath, FALSE);
		if (bRet) {
			aRetval = strNativePath;
			strNativePath += "/firefox/profiles.ini";

			FILE *pFile = ::fopen(strNativePath.c_str(), "r");
			if (pFile) {
				char szBuf[1024*16];
				size_t nRead = ::fread(szBuf, sizeof(char), sizeof(szBuf), pFile);
				if (nRead > 0) {
					szBuf[nRead] = '\0';
					LPCSTR pszStart = ::strstr(szBuf, "Path=");
					LPCSTR pszEnd = NULL;
					if (pszStart) {
						pszStart += ::strlen("Path=");
						pszEnd = ::strstr(pszStart, "\n");
					}
					if (pszEnd) {
						aRetval += "/firefox/";
						aRetval.append(pszStart, pszEnd);
						bRet = TRUE;
					}
				}
				::fclose(pFile);
			}
		}
	}
	else if (OpenWithoutRegName() && GetCurrentProfileDirectory(aRetval)) {
		bRet = TRUE;
	}

	if (bRet) {
		aRetval += RT_OS_SEPARATE;
		aRetval += "prefs.js";
	}
	return bRet;
}

BOOL CRtHttpMozillaProfile::OpenWithoutRegName()
{
	BOOL bRet = FALSE;
	CRtString strNativePath;
	
	bRet = GetNativeRegName(strNativePath);
	if (bRet)
		bRet = OpenRegFile(strNativePath);
	return bRet;
}

BOOL CRtHttpMozillaProfile::
GetKey(RKEY aBaseKey, LPCSTR aKeyname, RKEY &aRetval)
{
	BOOL bRet = FALSE;
	RT_ASSERTE_RETURN(aKeyname && m_newRegFile.fh, bRet);
	REGOFF      start;
	REGDESC     desc;
	
	start = nr_TranslateKey(&m_newRegFile, aBaseKey);
	if ( start != 0 ) {
		/* find the specified key ( if it's valid )*/
		bRet = nr_Find( &m_newRegFile, start, aKeyname, &desc, 0, 0);
		if (bRet) {
			aRetval = (RKEY)desc.location;
		}
	}
	return bRet;
}

BOOL CRtHttpMozillaProfile::
GetString(RKEY aBaseKey, LPCSTR aValname, CRtString &aRetval)
{
	BOOL bRet = FALSE;
	RT_ASSERTE_RETURN(aValname && m_newRegFile.fh, bRet);
	REGDESC     desc;

	bRet = nr_ReadDesc(&m_newRegFile, aBaseKey, &desc);
	if (bRet) {
		/* if the named entry exists */
		bRet = nr_FindAtLevel(&m_newRegFile, desc.value, aValname, &desc, NULL);
		if (bRet) {
			/* red the CRtString */
			if ( desc.type == REGTYPE_ENTRY_STRING_UTF) {
				char regStr[MAXREGPATHLEN];
				DWORD buflen = sizeof(regStr);
				RT_ASSERTE(desc.valuelen < buflen);
				bRet = nr_ReadFile(m_newRegFile.fh, desc.value, desc.valuelen, regStr);
				regStr[buflen-1] = '\0';
				if (bRet)
					aRetval.assign(regStr, desc.valuelen - 1);
			} else {
				bRet = FALSE;
			}
		}
	}
	return bRet;
}

BOOL CRtHttpMozillaProfile::GetCurrentProfileDirectory(CRtString &aRetval)
{
	BOOL bRet = FALSE;
	RT_ASSERTE_RETURN(m_newRegFile.fh, bRet);

	if (m_BrowserType == IRtHttpProxySetting::BROWSER_NETSCAPE2) {
		RKEY profilesTreeKey;
		bRet = GetKey(ROOTKEY_COMMON, kRegistryProfileSubtreeString, profilesTreeKey);
		if (!bRet) 
			return bRet;
		
		CRtString strCurrentProfile;
		bRet = GetString(profilesTreeKey, kRegistryCurrentProfileString, strCurrentProfile);
		if (!bRet) 
			return bRet;
		
		REGDESC desc;
		bRet = nr_Find(&m_newRegFile, profilesTreeKey, strCurrentProfile.c_str(), 
			&desc, NULL, NULL);
		if (bRet) {
			bRet = GetString(desc.location, kRegistryDirectoryString, aRetval);
		}
	} else {
#ifdef WIN32
		HKEY key;
		LONG ret = RegOpenKeyEx(HKEY_CURRENT_USER,
								"Software\\Netscape\\Netscape Navigator\\biff",
								0,
								KEY_QUERY_VALUE,
								&key);
		if(ret != ERROR_SUCCESS)
			return bRet;
		
		char user[1024];
		DWORD len = sizeof(user);
		ret = RegQueryValueEx(key, "CurrentUser", 0, 0, (LPBYTE)user, &len);
		RegCloseKey(key);
		if(ret == ERROR_SUCCESS) {
			REGDESC desc;
			bRet = nr_Find(&m_newRegFile, nr_TranslateKey(&m_newRegFile, ROOTKEY_USERS), 
				user, &desc, NULL, NULL);
			if (bRet) 
				bRet = GetString(desc.location, "ProfileLocation", aRetval);
		}
#endif // WIN32
	}

	if (!bRet) {
		RT_ERROR_TRACE("CRtHttpMozillaProfile::GetCurrentProfileDirectory");
	}
	return bRet;
}

BOOL CRtHttpMozillaProfile::
GetNativeRegName(CRtString &aRetval, BOOL aIncludeName)
{
	if (m_BrowserType == IRtHttpProxySetting::BROWSER_NETSCAPE1)
		return Get4xNativeRegName(aRetval);
	
	char szPath[_MAX_PATH];
	BOOL bRet = FALSE;

#if defined(WIN32)
	LPMALLOC pMalloc = NULL;
	LPITEMIDLIST pItemIDList = NULL;
	if (SUCCEEDED(SHGetMalloc(&pMalloc))  
		&& SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_APPDATA, &pItemIDList)) 
		&& SUCCEEDED(SHGetPathFromIDList(pItemIDList, szPath))) 
	{
		bRet = TRUE;
	} 
	else {
		// On some Win95 machines, CSIDL_APPDATA does not exist
		UINT len = GetWindowsDirectory(szPath, _MAX_PATH);
		if (len <= _MAX_PATH - 1)
			bRet = TRUE;
	}
#else // !WIN32
	LPCSTR pszHome = getenv("HOME");
	if (pszHome) {
		strncpy(szPath, pszHome, sizeof(szPath));
		szPath[sizeof(szPath) - 1] = '\0';
		bRet = TRUE;
	}
#endif // WIN32

	if (bRet) {
		aRetval = szPath;
#if defined WIN32 
		aRetval += "\\Mozilla";
		if (aIncludeName)
			aRetval += "\\registry.dat";
#else
		aRetval += "/.mozilla";
		if (aIncludeName)
			aRetval += "/appreg";
#endif // WIN32
	}

#ifdef WIN32
	if (pItemIDList && pMalloc)
		pMalloc->Free(pItemIDList); 
	if (pMalloc)
		pMalloc->Release();
#endif // WIN32

	if (!bRet) {
		RT_ERROR_TRACE("CRtHttpMozillaProfile::GetNativeRegName");
	}
	return bRet;
}

BOOL CRtHttpMozillaProfile::Get4xNativeRegName(CRtString &aRetval)
{
	BOOL bRet = FALSE;
#ifdef WIN32
	char szPath[_MAX_PATH];
	UINT len = GetWindowsDirectory(szPath, _MAX_PATH);
	if (len <= _MAX_PATH - 1)
		bRet = TRUE;

	if (bRet) {
		aRetval.assign(szPath, len);
		aRetval += "\\nsreg.dat";
	}
#endif // WIN32

	if (!bRet) {
		RT_ERROR_TRACE("CRtHttpMozillaProfile::Get4xNativeRegName");
	}
	return bRet;
}


static WORD nr_ReadShort(const char *buffer)
{
	BYTE *p = (BYTE*)buffer;
	WORD val = (WORD)(*p + (WORD)( *(p+1) * 0x100 ));
	return val;
}

static DWORD nr_ReadLong(const char *buffer)
{
	BYTE *p = (BYTE*)buffer;
	DWORD val = *p
		+ (DWORD)(*(p+1) * 0x100L)
		+ (DWORD)(*(p+2) * 0x10000L )
		+ (DWORD)(*(p+3) * 0x1000000L );
	return val;
}

static BOOL nr_InitStdRkeys( REGFILE *reg )
{
	BOOL err = TRUE;
	RKEY key;
	RT_ASSERTE( reg != NULL );
	memset( &reg->rkeys, 0, sizeof(STDNODES) );
	
	/* Add each key before looking it up.  Adding an already
	* existing key is harmless, and these MUST exist.
	*/
	
	/* ROOTKEY_USERS */
	err = nr_RegAddKey( reg, reg->hdr.root, ROOTKEY_USERS_STR, &key);
	if ( err != TRUE )
		return err;
	reg->rkeys.users = key;
	
	/* ROOTKEY_COMMON */
	err = nr_RegAddKey( reg, reg->hdr.root, ROOTKEY_COMMON_STR, &key);
	if ( err != TRUE ) 
		return err;
	reg->rkeys.common = key;
	
	/* ROOTKEY_VERSIONS */
	err = nr_RegAddKey( reg, reg->hdr.root, ROOTKEY_VERSIONS_STR, &key);
	if ( err != TRUE )
		return err;
	reg->rkeys.versions = key;
	
	/* ROOTKEY_CURRENT_USER */
	/* delay until first use -- see nr_TranslateKey */
	
	/* ROOTKEY_PRIVATE */
	err = nr_RegAddKey( reg, reg->hdr.root, ROOTKEY_PRIVATE_STR, &key);
	if ( err != TRUE ) 
		return err;
	reg->rkeys.privarea = key;
	
	return err;
}   /* nr_InitStdRkeys */

static BOOL nr_RegAddKey(REGFILE *reg, RKEY key, char *path, RKEY *newKey)
{
	BOOL        err;
	REGDESC     desc;
	REGOFF      start;
	RT_ASSERTE( reg != NULL );
	RT_ASSERTE( path != NULL );
	RT_ASSERTE( *path != '\0' );
	
	/* have to translate again in case this is an internal call */
	start = nr_TranslateKey( reg, key );
	if ( start == 0 )
		return FALSE;
	
	/* Get starting desc */
	err = nr_ReadDesc( reg, start, &desc );
	if ( err == TRUE) {
		/* look for name at next level down */
		err = nr_FindAtLevel(reg, desc.down, path, &desc, 0);
		if (err == TRUE && newKey != NULL) 
			*newKey = desc.location;
	} 
	return err;
}   /* nr_RegAddKey */

static REGOFF nr_TranslateKey( REGFILE *reg, RKEY key )
{
	REGOFF retKey = 0;
	
	/* if it's a special key  */
	if ( key < HDRRESERVE )  {
		switch (key)
		{
		case ROOTKEY:
			retKey = reg->hdr.root;
			break;
			
		case ROOTKEY_VERSIONS:
			retKey = reg->rkeys.versions;
			break;
			
		case ROOTKEY_USERS:
			retKey = reg->rkeys.users;
			break;
			
		case ROOTKEY_COMMON:
			retKey = reg->rkeys.common;
			break;
			
		case ROOTKEY_CURRENT_USER:
			RT_ASSERTE(FALSE);
			break;
			
		case ROOTKEY_PRIVATE:
			retKey = reg->rkeys.privarea;
			break;
			
		default:
			/* not a valid key */
			retKey = 0;
			break;
		}
	} else {
		/* ...otherwise it's fine as-is */
		retKey = (REGOFF)key;
	}
	return ( retKey );
}  /* nr_TranslateKey */

static BOOL nr_ReadDesc(REGFILE *reg, REGOFF offset, REGDESC *desc)
{
	BOOL err;
	char descBuf[ DESC_SIZE ];
	RT_ASSERTE(reg);
	RT_ASSERTE(offset >= HDRRESERVE);
	RT_ASSERTE(offset < reg->hdr.avail);
	RT_ASSERTE(desc);
	
	err = nr_ReadFile(reg->fh, offset, DESC_SIZE, &descBuf);
	if (err == TRUE)
	{
		desc->location  = nr_ReadLong ( descBuf + DESC_LOCATION );
		desc->name      = nr_ReadLong ( descBuf + DESC_NAME );
		desc->namelen   = nr_ReadShort( descBuf + DESC_NAMELEN );
		desc->type      = nr_ReadShort( descBuf + DESC_TYPE );
		desc->left      = nr_ReadLong ( descBuf + DESC_LEFT );
		desc->value     = nr_ReadLong ( descBuf + DESC_VALUE );
		desc->valuelen  = nr_ReadLong ( descBuf + DESC_VALUELEN );
		desc->parent    = nr_ReadLong ( descBuf + DESC_PARENT );
		
		if ( TYPE_IS_ENTRY(desc->type) ) {
			desc->down = 0;
			desc->valuebuf  = nr_ReadLong( descBuf + DESC_VALUEBUF );
		} else {  /* TYPE is KEY */
			desc->down      = nr_ReadLong( descBuf + DESC_DOWN );
			desc->valuebuf  = 0;
		}
		if (desc->location != offset)
			err = FALSE;
		else if ( desc->type & REGTYPE_DELETED )
			err = FALSE;
	}
	return err;
}   /* ReadDesc */

static BOOL nr_ReadFile(FILE *fh, REGOFF offset, INT32 len, void *buffer)
{
	BOOL bRet = FALSE;
	RT_ASSERTE(len > 0);
	RT_ASSERTE(buffer != NULL);
	RT_ASSERTE(fh != NULL);
	
	if (!fseek(fh, offset, SEEK_SET)) {
		size_t nRead = fread(buffer, 1, len, fh);
		if (nRead >= (size_t)len)
			bRet = TRUE;
	}
	return bRet;
}

static BOOL nr_FindAtLevel(REGFILE *reg, REGOFF offset, const char *pName,
						   REGDESC *pDesc, REGOFF *pOffPrev)
{
	char    namebuf[MAXREGNAMELEN];
	REGDESC desc;
	BOOL  err;
	REGOFF  prev = 0;
	
	/* Note: offset=0 when there's no 'down' or 'left' */
	RT_ASSERTE(reg);
	RT_ASSERTE(offset < reg->hdr.avail);
	RT_ASSERTE(pName);
	RT_ASSERTE(*pName);
	
	while ( offset != 0 )
	{
		/* get name of next node */
		err = nr_ReadDesc(reg, offset, &desc);
		if (err != TRUE)
			return err;
		
		err = nr_ReadName(reg, &desc, sizeof(namebuf), namebuf);
		if (err != TRUE)
			return err;
		
		/* check to see if it's the one we want */
		if (strcmp(namebuf, pName) == 0) {
			/* Found it! Signaled by non-zero offset */
			break;
		}
		
		/* advance to the next node */
		prev = offset;
		offset = desc.left;
	}
	
	if ( pDesc != NULL && (prev || offset)) {
		/* prev and offset BOTH null means we never loaded a desc */
		memcpy(pDesc, &desc, sizeof(REGDESC));
	}
	if ( pOffPrev != NULL ) {
		*pOffPrev = prev;
	}
	return offset != 0 ? TRUE : FALSE;
}   /* FindAtLevel */

static BOOL nr_Find(REGFILE *reg, REGOFF offParent, const char *pPath,
					REGDESC *pDesc, REGOFF *pPrev, REGOFF *pParent)
{
	BOOL  err;
	REGDESC desc;
	REGOFF  offPrev = 0;
	
	RT_ASSERTE( pPath != NULL );
	RT_ASSERTE( offParent >= HDRRESERVE );
	RT_ASSERTE( reg->fh );
	
	if (pPrev)
		*pPrev = 0;
	if (pParent)
		*pParent = 0;
	
	/* read starting desc */
	err = nr_ReadDesc( reg, offParent, &desc);
	if ( err == TRUE ) {
		/* save current location as parent of next segment */
		offParent = desc.location;
		/* look for name at next level down */
		err = nr_FindAtLevel(reg, desc.down, pPath, &desc, &offPrev);
	}
	
	if ( err == TRUE ) {
		if (pDesc) {
			memcpy(pDesc, &desc, sizeof(REGDESC));
		}
		if (pPrev) {
			*pPrev = offPrev;
		}
		if (pParent) {
			*pParent = offParent;
		}
	}
	return err;
}   /* nr_Find */

static BOOL nr_ReadName(REGFILE *reg, REGDESC *desc, DWORD buflen, char *buf)
{
	BOOL err = FALSE;
	RT_ASSERTE(reg);
	RT_ASSERTE(desc->name > 0);
	RT_ASSERTE(desc->name < reg->hdr.avail);
	RT_ASSERTE(buflen > 0);
	RT_ASSERTE(buf);
	if ( desc->namelen > buflen )
		return err;
	
	err = nr_ReadFile(reg->fh, desc->name, desc->namelen, buf);
	buf[buflen-1] = '\0';   /* avoid runaways */
	return err;
}   /* ReadName */
